﻿#include "ANPRCamera.h"
#include "XmlConfig.h"
#include <log4cxx/logger.h>
#include <boost/filesystem/operations.hpp>
#ifdef _WIN32
#include "windows.h"
#endif
#ifdef __linux__
#include <iconv.h>
#endif

void ANPRCamera::VideoDataCallback(int handle, unsigned char *buffer, int size, int width, int height, int type, void *context)
{
    ANPRCamera *anprCamera = (ANPRCamera*)context;

    int port;
    if (handle == anprCamera->handle[0])
        port = 0;
    else
        port = 1;

    if (anprCamera->captureImage == port || anprCamera->ANPRMode == 1 && port == 0)
    {
        if (anprCamera->image[port] == 0)
            anprCamera->image[port] = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);

        IplImage *image = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);

        if (type == 1 || type == 2) // YV12
        {
            IplImage *yuvImage = cvCreateImage(cvSize(width, height + height / 2), IPL_DEPTH_8U, 1);

            memcpy(yuvImage->imageData, buffer, size);
            cvCvtColor(yuvImage, image, CV_YUV2RGB_YV12);

            if (type == 2)
                cvCvtColor(image, image, CV_BGR2RGB);

            cvReleaseImage(&yuvImage);
        }
        else
        {
            memcpy(image->imageData, buffer, size);
        }

        std::string lpNumber = anprCamera->anpr->DetectLPNumber(image);

        if (anprCamera->ANPRMode == 0 || anprCamera->ANPRMode == 1 && lpNumber.size() > 0)
        {
            LOG4CXX_INFO(anprCamera->logger, "ANPRCamera::VideoDataCallback(): " << lpNumber);
            anprCamera->lpNumber[port] = lpNumber;
        }

        if (anprCamera->captureImage == port)
        {
            cvCopy(image, anprCamera->image[port]);

            std::string path;

            if (!anprCamera->imagePath[port].empty())
            {
                if (anprCamera->enableOutputPath)
                    path = anprCamera->outputPath + "/" + anprCamera->imagePath[port];
                else
                    path = anprCamera->imagePath[port];

                cv::imwrite(path.c_str(), cv::cvarrToMat(anprCamera->image[port]));
            }
        }
        else
        {
            if (anprCamera->ANPRMode == 1 && lpNumber.size() > 0)
                cvCopy(image, anprCamera->image[port]);
        }

        if (anprCamera->callback)
            anprCamera->callback(port, anprCamera->lpNumber[port].c_str(), anprCamera->imagePath[port].c_str(), anprCamera->context);

        anprCamera->captureImage = -1;

        cvReleaseImage(&image);
    }
}

void ANPRCamera::ANPRInfoCallback(int handle, const char *lpNumber, unsigned char* buffer, int size, int width, int height, void *context)
{
    ANPRCamera *camera = (ANPRCamera *)context;

    int port;
    if (handle == camera->handle[0])
        port = 0;
    else
        port = 1;
    
    if (camera->image[port] == 0)
    {
        camera->image[port] = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
    }

    memcpy(camera->image[port]->imageData, buffer, size);

#ifdef __linux__
    if (camera->charset_conv == GBK_TO_UTF8)
    {
        char lpNumberUTF8[32];
        size_t srclen = strlen(lpNumber);
        size_t destlen = sizeof(lpNumberUTF8);
        memset(lpNumberUTF8, 0, 32);

        camera->GBKToUTF8((char *)lpNumber, &srclen, lpNumberUTF8, &destlen);
        camera->lpNumber[port] = lpNumberUTF8;
        
    }
#else
    camera->lpNumber[port] = lpNumber;
#endif
    
    LOG4CXX_INFO(camera->logger, "ANPRCamera::ANPRInfoCallback(): port: " << port << ", " << camera->lpNumber[port]);

    if (camera->callback)
        camera->callback(port, camera->lpNumber[port].c_str(), camera->imagePath[port].c_str(), camera->context);
}

#ifdef _WIN32
std::string ANPRCamera::UTF8ToGBK(const std::string& str)
{
    int wchar_len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
    wchar_t* wchar_buf = new wchar_t[wchar_len + 1];
    memset(wchar_buf, 0, wchar_len * 2 + 2);
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), wchar_buf, wchar_len);

    int char_len = WideCharToMultiByte(CP_ACP, 0, wchar_buf, -1, NULL, NULL, NULL, NULL);
    char* buf = new char[char_len + 1];
    memset(buf, 0, char_len + 1);
    WideCharToMultiByte(CP_ACP, 0, wchar_buf, wchar_len, buf, char_len, NULL, NULL);

    std::string ret_str = buf;

    delete[]buf;
    delete[]wchar_buf;

    return ret_str;
}
#endif

#ifdef __linux__
int ANPRCamera::GBKToUTF8(char* src, size_t* srclen, char* dest, size_t* destlen)
{
    iconv_t cd = iconv_open("UTF-8", "GBK");

    if (cd == (iconv_t)-1)
    {
        printf("iconv_open() err : %s\n", strerror(errno));
        return -1;
    }

    size_t rc = iconv(cd, &src, srclen, &dest, destlen);

    if (rc == (size_t)-1)
    {
        printf("iconv() err : %s\n", strerror(errno));
        return -1;
    }

    iconv_close(cd);

    return 0;
}
#endif

ANPRCamera::ANPRCamera()
{
    logger = log4cxx::Logger::getLogger("ANPRCamera");

    captureImage = -1;
    ANPRMode = 0;
    minPlateWidth = 80;
    maxPlateWidth = 200;
    maxImageWidth = 720;
    maxImageHeight = 576;
    enableDoubleRowYellowLP = false;
    enableDoubleRowPoliceLP = false;
    enableDoubleRowArmyLP = false;
    enableEmbassyLP = false;
    enableTractorLP = false;
    enableAirportLP = false;
    enableRegion = false;

    LoadConfigParams();
    
    for (int i = 0; i < MAX_CAMERA_NUM; i++)
    {
        camera[i] = 0;
        image[i] = 0;
        handle[i] = -1;
    }

    anpr = new ANPRTH;

    RECT *rect = 0;
    if (enableRegion)
        rect = &region;

    if (anpr->Init(ANPRMode, minPlateWidth, maxPlateWidth, maxImageWidth, maxImageHeight, rect,
        enableDoubleRowYellowLP, enableDoubleRowPoliceLP, enableDoubleRowArmyLP, enableEmbassyLP, enableTractorLP, enableAirportLP, province) < 0)
    {
        LOG4CXX_ERROR(logger, "ANPRTH::Init() error");
    }
}

ANPRCamera::~ANPRCamera()
{
    for (int i = 0; i < MAX_CAMERA_NUM; i++)
    {
        if (image[i] != 0)
        {
            cvReleaseImage(&image[i]);
            image[i] = 0;
        }
    }

    for (int i = 0; i < MAX_CAMERA_NUM; i++)
    {
        if (camera[i] != 0)
        {
            switch (camera[i]->type)
            {
            case CAMERA_TYPE::FFMPEG_CAMERA:
                delete (FFmpegCamera *)camera[i];
                break;
#ifndef __aarch64__
            case CAMERA_TYPE::HIK_CAMERA:
                delete (HikCamera *)camera[i];
                break;
#endif
#ifdef _WIN32
            case CAMERA_TYPE::ICE_CAMERA:
                delete (ICECamera *)camera[i];
                break;
#endif
            }

            camera[i] = 0;
        }
    }

    if (anpr) delete anpr;
}

void ANPRCamera::LoadConfigParams()
{
#ifdef _WIN32
    char temp[MAX_PATH];
    GetModuleFileName(NULL, temp, MAX_PATH);
    
    configFilePath = temp;
    configFilePath = configFilePath.substr(0, configFilePath.find_last_of('\\'));
    configFilePath += "\\UVSSVideoCamera.xml";
#elif __linux__
    configFilePath = boost::filesystem::initial_path<boost::filesystem::path>().string() + "/UVSSVideoCamera.xml";
#endif

    XmlConfig config;
    if (!config.Open(configFilePath.c_str()))
        return;

    ANPRMode = config.GetIntValue("ANPR", "Mode", 0);
    minPlateWidth = config.GetIntValue("ANPR", "MinPlateWidth", 80);
    maxPlateWidth = config.GetIntValue("ANPR", "MaxPlateWidth", 200);
    maxImageWidth = config.GetIntValue("ANPR", "MaxImageWidth", 720);
    maxImageHeight = config.GetIntValue("ANPR", "MaxImageHeight", 576);

#ifdef _WIN32
    std::string encoding = config.GetEncoding();

    if (encoding == "utf-8")
    {
        province = UTF8ToGBK(config.Get("ANPR", "Province", ""));
    }
    else
        province = config.Get("ANPR", "Province", "");
#else
        province = config.Get("ANPR", "Province", "");
#endif

    if (config.GetIntValue("ANPR", "DoubleRowYellowLP", 0))
        enableDoubleRowYellowLP = true;

    if (config.GetIntValue("ANPR", "DoubleRowPoliceLP", 0))
        enableDoubleRowPoliceLP = true;

    if (config.GetIntValue("ANPR", "DoubleRowArmyLP", 0))
        enableDoubleRowArmyLP = true;

    if (config.GetIntValue("ANPR", "EmbassyLP", 0))
        enableEmbassyLP = true;

    if (config.GetIntValue("ANPR", "TractorLP", 0))
        enableTractorLP = true;

    if (config.GetIntValue("ANPR", "AirportLP", 0))
        enableAirportLP = true;

    if (config.GetIntValue("ANPR", "EnableRegion", 0))
    {
        enableRegion = true;
        region.left = config.GetIntValue("ANPR", "RegionLeft", 0);
        region.right = config.GetIntValue("ANPR", "RegionRight", 720);
        region.top = config.GetIntValue("ANPR", "RegionTop", 0);
        region.bottom = config.GetIntValue("ANPR", "RegionBottom", 576);
    }
}

void ANPRCamera::Init(int index, HWND hwnd)
{
    XmlConfig config;

    if (config.Open(configFilePath.c_str()))
    {
        std::string elementName = "ANPRCamera";
        switch (index)
        {
        case 0:
            elementName += "1";
            break;
        case 1:
            elementName += "2";
            break;
        }

        int cameraType = config.GetIntValue(elementName.c_str(), "Type", 3);

        std::string IPAddress;
        int port;
        std::string username;
        std::string password;
        int channel;
        int stream;
        char URL[1024];
        std::string path;

        channel = config.GetIntValue(elementName.c_str(), "Channel", 0);
        stream = config.GetIntValue(elementName.c_str(), "Stream", 0);

        switch (cameraType)
        {
        case 1:
        case 2:
            camera[index] = new FFmpegCamera;
            path = config.Get(elementName.c_str(), "Path", "");

            ((FFmpegCamera *)camera[index])->Init(hwnd, path.c_str());
            if (config.GetIntValue(elementName.c_str(), "RenderMode", 1) == 1)
                ((FFmpegCamera *)camera[index])->hardwareRender = true;
            else
                ((FFmpegCamera *)camera[index])->hardwareRender = false;

            if (cameraType == 2)
                ((FFmpegCamera *)camera[index])->device = true;
            camera[index]->SetCallback(ANPRCamera::VideoDataCallback, this);
        break;
#ifndef __aarch64__
        case 3:
            camera[index] = new HikCamera;

            IPAddress = config.Get(elementName.c_str(), "IPAddress", "192.168.1.64");
            port = config.GetIntValue(elementName.c_str(), "Port", 8000);
            username = config.Get(elementName.c_str(), "Username", "admin");
            password = config.Get(elementName.c_str(), "Password", "password");

            ((HikCamera *)camera[index])->Init(hwnd, IPAddress.c_str(), port, username.c_str(), password.c_str(), channel, stream);
            if (ANPRMode == 2)
            {
                LOG4CXX_INFO(logger, "HikCamera: ANPR mode");
                ((HikCamera *)camera[index])->SetANPRInfoCallback(ANPRCamera::ANPRInfoCallback, this);
            }
            else
                camera[index]->SetCallback(ANPRCamera::VideoDataCallback, this);

#ifdef __linux__
            charset_conv = GBK_TO_UTF8;
#endif
            break;
#endif
#ifdef _WIN32
        case 4:
            camera[index] = new ICECamera;

            ((ICECamera *)camera[index])->SetANPRInfoCallback(ANPRCamera::ANPRInfoCallback, this);

            IPAddress = config.Get(elementName.c_str(), "IPAddress", "192.168.1.100");
            port = config.GetIntValue(elementName.c_str(), "Port", 8000);
            username = config.Get(elementName.c_str(), "", "");
            password = config.Get(elementName.c_str(), "", "");

            ((ICECamera *)camera[index])->Init(hwnd, IPAddress.c_str(), port, username.c_str(), password.c_str(), channel, stream);
            break;
#endif
        default:
            return;
        }

        handle[index] = camera[index]->handle;

        LOG4CXX_INFO(logger, "ANPRCamera::Init(): Camera type: " << cameraType << ", IP address: " << IPAddress << ", port: " << port <<
            ", username: " << username << ", password: " << password << ", path: " << path);

        handle[index] = camera[index]->handle;
    }
}

int ANPRCamera::Connect(int index)
{
    if (camera[index] != 0)
    {
        int ret = camera[index]->Connect();

        if (ret != 0)
        {
            LOG4CXX_ERROR(logger, "ANPRCamera::OpenVideo failed, camera[" << index << "]: " << ret);
        }

        return ret;
    }
}

int ANPRCamera::Disconnect(int index)
{
    if (camera[index] != 0)
        return camera[index]->Disconnect();
}

int ANPRCamera::StartSaveVideo(int index, const char *videoPath)
{
    if (camera[index]->type == HIK_CAMERA)
    {
        return ((HikCamera *)camera[index])->StartSaveVideo(videoPath);
    }
    else
        return -1;
}

int ANPRCamera::StopSaveVideo(int index)
{
    if (camera[index]->type == HIK_CAMERA)
    {
        return ((HikCamera *)camera[index])->StopSaveVideo();
    }
    else
        return -1;   
}
